/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.matching.tagvalue;

import java.util.Hashtable;

import cgl.narada.matching.EventDestinations;

/**
   This class implements the matching tree node. This node has edges 
   originating from it, some of which could be pure destination edges and some
   of which could connect this tag node to some other tag node.
   @author Shrideep Pallickara
   $Revision$
   $Date$
 */ 


public class MatchingTreeNode implements MatchingDebugFlags {

  String tag;
  /** For each entry from tagsFromNode we have the list of values that lead
      to the specified tag vlaues */
  private Hashtable edgesFromNode;
  private Hashtable destinationEdges;
  private boolean containsDestinationEdges = false;
  private boolean containsStarDestinationEdge = false;
  
  /** Constructor for the matching tree node. This initializes a tag node for
      the profile graph that is being hosted at this node. This tag node would
      be reused if the sequence of tag-value pairs entering this node is 
      identical to the one responsible for the creation of this node.
      @param tag The tag for the node in the graph.
  */
  public MatchingTreeNode(String tag) {
    this.tag = tag;
    edgesFromNode= new Hashtable();
    if (MatchingTreeNode_Debug) {
      System.out.println("Creating Node (" + tag+")"); 
    }
  }
  
  /** This method returns the tag associated with the node.
      @return The tag associated with the node.
  */
  public String getTag() {
    return tag;
  }
  
  /** This method is responsible for matching an event to the profile graph
      that is stored at the node. If there are any pure destination edges 
      originating from this node - both *, and the value contained in the tag
      value pair, those destinations need to be added to the destination list.
      The matching then proceeds recursively through the profile graph 
      computing destinations assocaited with the ordered set of tag-value 
      pairs.
      @param e The event that needs to be matched.
      @param _destinationsComputedSoFar The destinations that have been computed
      so far. This is needed since the method in question is a recursive 
      method.
      @return The destinations computed so far.
  */  
  public int matchEvent(MEvent e, 
			int _destinationsComputedSoFar) {
    String value = e.getTagValue(tag);
    if (containsDestinationEdges ) {
      /** This implies that we do possess destination edges here. Thus 
	  destinationEdges is not NULL*/
      if (destinationEdges.containsKey(value)) {
	MatchingTreeEdge destEdge = 
	  (MatchingTreeEdge)destinationEdges.get(value);
	_destinationsComputedSoFar |= destEdge.getDestinations();
      }
      /** If there are any * destination edges, we ofcourse need to add them
	  to the destination lists */
      if (containsStarDestinationEdge) {
	MatchingTreeEdge destEdge = 
	  (MatchingTreeEdge)destinationEdges.get("*");
	_destinationsComputedSoFar |= destEdge.getDestinations();
      }
    }
    
    /** Now that the destinations have been updated (if there were any
	destination edges at this node), we proceed with further calculations
	of destinations */
    String tagTwo = e.getNextTag();
    if (tagTwo == null) {
      if (MatchingTreeNode_Debug) {
	System.out.println("MatchingTreeNode:matchEvent() => " + 
			   " No further matching required ");
      }
      return  _destinationsComputedSoFar;
    }
    String key = value +"::" + tagTwo;
    if (edgesFromNode.containsKey(key)) {
      MatchingTreeNode node = 
	((MatchingTreeEdge) edgesFromNode.get(key)).getLeadingIntoNode();
      _destinationsComputedSoFar =
	node.matchEvent(e, _destinationsComputedSoFar);
      // System.out.println(_destinationsComputedSoFar);
    }
    key = "*::" + tagTwo;
    if (edgesFromNode.containsKey(key)) {
      MatchingTreeNode node = 
	((MatchingTreeEdge) edgesFromNode.get(key)).getLeadingIntoNode();
      _destinationsComputedSoFar =
	node.matchEvent(e, _destinationsComputedSoFar);
      //System.out.println(_destinationsComputedSoFar);
    }
    return _destinationsComputedSoFar;
  }
  
  
  /** This method is responsible for matching an event to the profile graph
      that is stored at the node. If there are any pure destination edges 
      originating from this node - both *, and the value contained in the tag
      value pair, those destinations need to be added to the destination list.
      The matching then proceeds recursively through the profile graph 
      computing destinations assocaited with the ordered set of tag-value 
      pairs. The results returned are specific to facilitating the storing of 
      the event, along with its associated destinations and predicateCount
      list, to stable storage.
      @param e The event that needs to be matched.
      @param _destinationsComputedSoFar The destinations that have been computed
      so far. This is needed since the method in question is a recursive 
      method.
   */  
  public void matchEventForStorage(MEvent e, 
			      EventDestinations _destinationsComputedSoFar) {
    String value = e.getTagValue(tag);
    if (containsDestinationEdges ) {
      /** This implies that we do possess destination edges here. Thus 
	  destinationEdges is not NULL*/
      if (destinationEdges.containsKey(value)) {
	MatchingTreeEdge destEdge = 
	  (MatchingTreeEdge)destinationEdges.get(value);
	_destinationsComputedSoFar.updateWithDestinationsAndPredicateCount(destEdge.getDestinations(), destEdge.getPredicateCountForDestinations());	
      }
      /** If there are any * destination edges, we ofcourse need to add them
	  to the destination lists */
      if (containsStarDestinationEdge) {
	MatchingTreeEdge destEdge = 
	  (MatchingTreeEdge)destinationEdges.get("*");
	_destinationsComputedSoFar.updateWithDestinationsAndPredicateCount(destEdge.getDestinations(), destEdge.getPredicateCountForDestinations());
      }
    }
    
    /** Now that the destinations have been updated (if there were any
	destination edges at this node), we proceed with further calculations
	of destinations */
    String tagTwo = e.getNextTag();
    if (tagTwo == null) {
      System.out.println("MatchingTreeNode::No further matching required ");
      return;
    }
    String key = value +"::" + tagTwo;
    if (edgesFromNode.containsKey(key)) {
      MatchingTreeNode node = 
	((MatchingTreeEdge) edgesFromNode.get(key)).getLeadingIntoNode();
      node.matchEventForStorage(e, _destinationsComputedSoFar);
      // System.out.println(_destinationsComputedSoFar);
    }
    key = "*::" + tagTwo;
    if (edgesFromNode.containsKey(key)) {
      MatchingTreeNode node = 
	((MatchingTreeEdge) edgesFromNode.get(key)).getLeadingIntoNode();
      node.matchEventForStorage(e, _destinationsComputedSoFar);
      //System.out.println(_destinationsComputedSoFar);
    }
  }


  /** This method is responsible for the creation of an edge between two
      tags, with the edge having a certain value. The two tags involved in the
      creation of the edge are 
      (a) The tag for this node 
      (b) The tagTwo for the subscription in question.
      @param value The value of the tag for the node in question
      @param tagTwo The next tag in the tag-value pair.
      @param destination The destinations associated with the edge.
  */
  public MatchingTreeEdge createEdgeLeadingIntoNode(String value, 
						    String tagTwo, 
						    int destination) {
    MatchingTreeEdge edge;
    String key = value + "::" + tagTwo;
    
    if (!edgesFromNode.containsKey(key)) {
      if (MatchingTreeNode_Debug) {
	System.out.println("Creating edge V=" +value + " between tags ("+
			   tag + ") and (" + tagTwo +")");
      }
      edge = 
	new MatchingTreeEdge(value, new MatchingTreeNode(tagTwo), destination);
      edgesFromNode.put(key, edge);
    } else {
      if (MatchingTreeNode_Debug) {
	System.out.println("Reusing edge V=" +value + " between tags ("+
			   tag + ") and (" + tagTwo +")");
      }
      edge = (MatchingTreeEdge)edgesFromNode.get(key);
      edge.addDestinationsToEdge(destination);
    }
   
    return edge;
  }
  /** Returns true if a new dge has been created, else it returns false if a
      new edge in not created */
  public boolean createEdgeWithDestinations(String value, 
					 int destinations) {
    MatchingTreeEdge destinationEdge;
   
    if (destinationEdges == null) {
      destinationEdges = new Hashtable();
      containsDestinationEdges = true;
    }
    
    if (value.equals("*")) {
      containsStarDestinationEdge = true;
    }

    if (destinationEdges.containsKey(value)) {
      destinationEdge = ((MatchingTreeEdge)destinationEdges.get(value));
      destinationEdge.addDestinationsToEdge(destinations);
      return false;
    }
    
    /** Else proceed to add the new destination edge to destinationEdge 
	hashtable */
    destinationEdge = 
      new MatchingTreeEdge(value, destinations);
    destinationEdges.put(value, destinationEdge);   
    return true;
  }
  
  /** This method is responsible for the removal of an edge leading into the
      node that would have a tag of <i>tagTwo</i>. The edge is removed
      only if the predicate count associated with all the destinations 
      contained within the edge is reduced to zero.
      @param value The value of the tag for the node in question
      @param tagTwo The next tag in the tag-value pair.
      @param destinations The destinations associated with the edge.
      @return NULL If the edge  is removed completely, since the predicate
      count is zero. <i>Otherwise</i>
      <br>Returns the edge created using this value.
  */
  public MatchingTreeEdge removeEdgeLeadingIntoNode(String value, 
						    String tagTwo, 
						    int destinations) {
    MatchingTreeEdge edge;
    String key = value + "::" + tagTwo;
    if (edgesFromNode.containsKey(key)) {
      edge = (MatchingTreeEdge)edgesFromNode.get(key);
      edge.removeDestinationsFromEdge(destinations);
      
      if (edge.getDestinations() == 0) {
	if (MatchingTreeNode_Debug) {
	  System.out.println("Removing the edge connecting tag (" + tag + ")" +
			     " val -> " + value +  " tag ("  + tagTwo + ")" );
	}
	edgesFromNode.remove(key);
	edge = null;
      }
      return edge;
    }
    
    System.out.println("MatchingTreeNode:removeEdgeLeadingIntoNode:" + 
		       "We are not aware of this particular subscription");
    return null;
  }

  
  /** This method returns true if the change needs to be propagated, and
      false otherwise
      @param value The value of the destination edge from a node.
      @param destinations The destination that is associated with the value
      @return <i>true</i> if the change needs to be propagated
  */
   public boolean removeEdgeWithDestinations(String value, 
					   int destinations) {
     MatchingTreeEdge destinationEdge;
     
     if (destinationEdges.containsKey(value)) {
       destinationEdge = ((MatchingTreeEdge)destinationEdges.get(value));
       destinationEdge.removeDestinationsFromEdge(destinations);
       if (destinationEdge.getDestinations() == 0) {
	 destinationEdges.remove(value);
	 if (value.equals("*")) {
	   containsStarDestinationEdge = false;
	 }
	 if (MatchingTreeNode_Debug) {
	   System.out.println("MatchingTreeNode:removeEdgeWithDestinations:"+
			      "Removing edge with value -> " + value + 
			      "Originating from tag (" + tag +")");
	 }
	 return true;
       }
       
       return false;
     }
     
     System.out.println("MatchingTreeNode:removeEdgeWithDestinations:" + 
			"The subscription is not available in the " +
			"matching tree ");
     return false;
   }
  
  
  /** This method is used to handle subscriptions that end at this particular
      value of the edge. We use this list of matching tree edges and update the
      destinations assocaited with these edges in case of an addition/deletion
      of the subscription predicate 
  */
  public MatchingTreeEdge[] getListOfEdgesWithValue(String value) {
    return null;
    
  } 
  
  /** This method retrieves the edge (of a certain value) emanating from this 
      particular node, and leading into another tag. If such an edge exists,
      this method returns that edge. If such a node does not exist this method
      should return a null value 
  */
  public MatchingTreeEdge getEdgeLeadingIntoTag(String tag, String value) {
    return null;
    
  }
}







